package discovery;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.log4j.Logger;

import utility.ConfigManager;

public class DiscoveryCoordinator {
	
	private Map<String, String> collectedNodes = new HashMap<String, String>();
	private ConfigManager config;

	Logger logger = Logger.getLogger(this.getClass());
	
	private class DiscoveryClientThread extends Thread {
		private DatagramPacket packet;
		
		public DiscoveryClientThread(DatagramPacket packet) {
			this.packet = packet;
		}
		
		/* (non-Javadoc)
		 * @see java.lang.Thread#run()
		 */
		@Override
		public void run() {
			String data = new String(packet.getData(), 0, packet.getLength());
			
			collectedNodes.put(data, packet.getAddress().getHostAddress());
			
			logger.debug("packet received from: "+packet.getAddress().getHostAddress());
		    logger.debug("content: "+ data);
		}		
	}
	
	public DiscoveryCoordinator() throws ConfigurationException {
		config = ConfigManager.getInstance();
	}
	
	/**
	 * Discovers the available test nodes via multicast.
	 * 
	 * @return a Map with a unique node name as key and the ip address as value
	 * @throws IOException
	 */
	public Map<String, String> getAvailableNodes() throws IOException {
		collectedNodes.clear();
		
		int sendPort = config.getInt("testnodeMulticastPort");
		int receivePort = config.getInt("coordinatorMulticastPort");
		String registryAddress = config.getString("registryAddress");
		int registryPort = config.getInt("registryPort");
		
		MulticastSocket socket = new MulticastSocket(receivePort);
		InetAddress group = InetAddress.getByName(config.getString("multicastAddress"));
		socket.joinGroup(group);
		
		byte[] content = (registryAddress + ":" + registryPort).getBytes();

		DatagramPacket packet = new DatagramPacket(content, content.length, group, sendPort);
		socket.send(packet);
		
		socket.setSoTimeout(10000);
		
		try {
			while(true) {
				byte[] buf = new byte[256];
			    packet = new DatagramPacket(buf, buf.length);
			    socket.receive(packet);
			    
			    (new DiscoveryClientThread(packet)).start();
			}
		} catch (SocketTimeoutException e) {
			return collectedNodes;
		}
	}

	/**
	 * Discovers the available test nodes via unicast on the basis of the given list of node.
	 * The list should contain the node dns name or the ip address as String.
	 * 
	 * @param nodes
	 * @return a Map with a unique node name as key and the ip address as value
	 */
	public Map<String, String> getAvailableNodes(List<String> nodes) throws IOException {
		collectedNodes.clear();
		
		int sendPort = config.getInt("testnodeMulticastPort");
		int receivePort = config.getInt("coordinatorMulticastPort");
		String registryAddress = config.getString("registryAddress");
		int registryPort = config.getInt("registryPort");
		
		DatagramSocket socket = new DatagramSocket(receivePort);
		DatagramPacket packet;
		byte[] content = (registryAddress + ":" + registryPort).getBytes();
		
		InetAddress localAddr = InetAddress.getLocalHost();
		for(String nodeAddress: nodes) {
			InetAddress iAddr = InetAddress.getByName(nodeAddress);
			// send to localhost if the nodeAddress is the address of the coordinator device
			if(iAddr.equals(localAddr))
				iAddr = InetAddress.getLoopbackAddress();
			
			packet = new DatagramPacket(content, content.length, iAddr, sendPort);
			logger.debug("Sending unicast paket with registry info '" + (registryAddress + ":" + registryPort) + "' to node with IP " + iAddr);
			socket.send(packet);
		}
		
		socket.setSoTimeout(10000);
		
		try {
			while(true) {
				byte[] buf = new byte[256];
			    packet = new DatagramPacket(buf, buf.length);
			    socket.receive(packet);
			    
			    (new DiscoveryClientThread(packet)).start();
			}
		} catch (SocketTimeoutException e) {
			return collectedNodes;
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {		
		try {
			DiscoveryCoordinator client = new DiscoveryCoordinator();
			
			List<String> preconfiguredNodes = Arrays.asList(client.config.getStringArray("testNodeAddress"));
			
//			Map<String, String> result = client.getAvailableNodes();
			
			Map<String, String> result = client.getAvailableNodes(preconfiguredNodes);
			
			System.out.println(result);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
